
module sdram_model(
    input       clk,
    input       rst_n,
    input [12:0] addr,
    input [1:0]  ba,
    input        cas_n,
    input        ras_n,
    input        we_n,
    input        cke,
    input [1:0]  cs_n,
    inout [31:0] dq,
    input [1:0]  dqmh,
    input [1:0]  dqml
);
    // 下面参数都是周期�?
    parameter tRC = 6; // 60 两次刷新或两次激活之间的间隔
    parameter tRP = 2; // 18 预充电到�?活的间隔
    parameter tRCD = 2; // 18 �?活到读写之间的间�?
    parameter tDPL = 2; // 12 数据输入到预充电间隔
    parameter tDAL = 3; // 30 数据输入到激�?/刷新间隔
    parameter tMRD = 2; // 模式寄存器配置时�?
    parameter tREF = 64; // ms 刷新周期时间
    parameter tRAS = 5; // �?活到预充�?

    // ~cs_n
    wire cmd_nop        = cke & ras_n & cas_n & we_n;
    wire cmd_bst        = cke & ras_n & cas_n & ~we_n;
    wire cmd_read       = cke & ras_n & ~cas_n & we_n & ~addr[10];
    wire cmd_read_pre   = cke & ras_n & ~cas_n & we_n & addr[10];
    wire cmd_write      = cke & ras_n & ~cas_n & ~we_n & ~addr[10];
    wire cmd_write_pre  = cke & ras_n & ~cas_n & ~we_n & addr[10];
    wire cmd_act        = cke & ~ras_n & cas_n & we_n;
    wire cmd_pre        = cke & ~ras_n & cas_n & ~we_n & ~addr[10];
    wire cmd_pall       = cke & ~ras_n & cas_n & ~we_n & addr[10]; // precharge all
    wire cmd_self       = cke & ~ras_n & ~cas_n & we_n;
    wire cmd_mrs        = cke & ~ras_n & ~cas_n & ~we_n & ~ba[1] & ~ba[0] & ~addr[10];



    parameter IDLE      = 4'b0000;
    parameter SELF      = 4'b0001; // self refresh
    parameter MRS       = 4'b0010;
    parameter ACT       = 4'b0011;
    parameter READ      = 4'b0100;
    parameter WRITE     = 4'b0110;
    parameter PRE       = 4'b1000;

    reg [3:0] state;
    reg [3:0] counter;
    reg [14:0] row_bank;
    reg [9:0] col;
    reg [3:0] remain_bl;
    // not consider cs
    reg [32767:0][1023:0][31:0] mem;
    wire cs_sel;
    wire [3:0] idle_state_n;
    wire idle_exit;
    wire act_exit;
    wire [3:0] act_state_n;
    wire read_exit;
    wire [3:0] read_state_n;
    wire counter_0;

    reg [11:0] mrs; // 模式寄存�?
    wire [2:0] tCAS     = mrs[6:4];
    wire [2:0] bl       = mrs[2:0];
    wire burstw         = ~mrs[9];

    wire state_idle = state == IDLE;
    wire state_act  = state == ACT;
    wire state_read = state == READ;
    wire state_write = state == WRITE;

    assign counter_0 = counter == 0;
    wire cas_end = counter <= tRAS - tRCD;
    wire remain_bl_0 = remain_bl == 0;
    wire rvalid = state_read & ~remain_bl_0 & counter_0;
    wire wvalid = state_write & ~remain_bl_0;

    assign idle_exit    = cmd_act | cmd_self | cmd_mrs;
    assign idle_state_n = {4{cmd_act}} & ACT |
                          {4{cmd_self}} & SELF |
                          {4{cmd_mrs}} & MRS;
    assign act_exit     = (cmd_read | cmd_write | cmd_read_pre | cmd_write_pre) |
                          (cmd_pre | cmd_pall);
    assign act_state_n  = {4{cmd_read}} & READ |
                          {4{cmd_write}} & WRITE |
                          {4{cmd_pre | cmd_read_pre | cmd_write_pre}} & PRE |
                          {4{cmd_pall}} & PRE;
    assign read_exit    = cmd_write | cmd_pre | cmd_pall;
    assign write_exit   = cmd_read | cmd_pre | cmd_pall;
    assign read_state_n = {4{cmd_write}} & WRITE |
                          {4{cmd_pre | cmd_pall}} & PRE;
    assign write_state_n = {4{cmd_read}} & READ |
                           {4{cmd_pre | cmd_pall}} & PRE;
    wire [31:0] rdata =  mem[row_bank][col];
    reg rvalid_n1;
    reg [31:0] rdata_n1;
    always_ff @(posedge clk)begin
        rvalid_n1 <= rvalid;
        rdata_n1 <= rdata;
    end
    assign dq = rvalid_n1 ? rdata_n1 : {32{1'bz}};

    always @(posedge clk)begin
        if(~rst_n)begin
            state <= 0;
            counter <= 0;
        end
        else begin
            if(counter != 0)begin
                counter <= counter - 1;
            end
            case(state)
            IDLE: begin
                if(idle_exit) state <= idle_state_n;
            end
            ACT: begin
                if(act_exit) state <= act_state_n;
            end
            READ: begin
                if(read_exit) state <= read_state_n;
            end
            WRITE: begin
                if(write_exit) state <= write_state_n;
            end
            PRE: begin
                state <= IDLE;
            end
            SELF: begin
                state <= IDLE;
            end
            MRS: begin
                state <= IDLE;
            end
            endcase
        end
    end

    wire [10:0] col_n = col + 1;

    reg [31:0] dq_r;
    reg [1:0] dqmh_r, dqml_r;

    always @(posedge clk)begin
        dq_r <= dq;
        dqmh_r <= dqmh;
        dqml_r <= dqml;
        if(state_idle && cmd_act)begin
            row_bank <= {ba, addr};
        end
        else if(rvalid | wvalid & burstw)begin
            row_bank <= row_bank + col_n[10];
        end
        if(state_act & (cmd_read | cmd_write) |
           state_read & cmd_read |
           state_write & cmd_write)begin
            col <= addr[9:0];
        end
        else if(rvalid | wvalid & burstw)begin
            col <= col_n[9:0];
        end
        if(~rst_n)begin
            remain_bl <= 0;
        end
        else if(state_act & (cmd_read | cmd_write) |
                state_read & cmd_read |
                state_write & cmd_write)begin
            remain_bl <= cmd_write & ~burstw ? 1 : (1 << bl);
        end
        else if(~remain_bl_0 & counter_0)begin
            remain_bl <= remain_bl - 1;
        end
    end

    always @(posedge clk)begin
        if(wvalid)begin
                mem[row_bank][col] <= dq_r & {{8{~dqmh_r[1]}}, {8{~dqmh_r[0]}}, {8{~dqml_r[1]}}, {8{~dqml_r[0]}}} | 
                             mem[row_bank][col] & ({{8{dqmh_r[1]}}, {8{dqmh_r[0]}}, {8{dqml_r[1]}}, {8{dqml_r[0]}}});
        end
    end

    always @(posedge clk)begin
        if(~rst_n)begin
            mrs <= 0;
        end
        else begin
            if(state_idle && cmd_mrs)begin
                mrs <= addr[9:0];
            end
        end
    end

// assert
    reg [3:0] rc_pre_counter;
    reg [3:0] rc_self_counter;
    reg [3:0] rp_counter;
    reg [3:0] rcd_counter;
    reg [3:0] dpl_counter;
    reg [3:0] dal_counter;
    reg [3:0] mrd_counter;
    reg [3:0] ras_counter;

    always @(posedge clk)begin
        if(~rst_n)begin
            rc_pre_counter <= 0;
            rc_self_counter <= 0;
            rp_counter <= 0;
            rcd_counter <= 0;
            dpl_counter <= 0;
            dal_counter <= 0;
            mrd_counter <= 0;
            ras_counter <= 0;
        end
        else begin
            if(cmd_act)begin
                rc_pre_counter <= tRC - 1;
            end
            else if(rc_pre_counter != 0) begin
                rc_pre_counter <= rc_pre_counter - 1;
            end
            if(rc_pre_counter != 0 && cmd_act)begin
                $display("[%t] tRC violation pre", $time);
            end

            if(cmd_self)begin
                rc_self_counter <= tRC - 1;
            end
            else if(rc_self_counter != 0) begin
                rc_self_counter <= rc_self_counter - 1;
            end
            if(rc_self_counter != 0 && cmd_self)begin
                $display("[%t] tRC violation self", $time);
            end

            if(cmd_pre | cmd_act)begin
                rp_counter <= tRP - 1;
            end
            else if(rp_counter != 0) begin
                rp_counter <= rp_counter - 1;
            end
            if(rp_counter != 0 && cmd_act)begin
                $display("[%t] tRP violation", $time);
            end

            if(cmd_act)begin
                rcd_counter <= tRCD - 1;
            end
            else if(rcd_counter != 0) begin
                rcd_counter <= rcd_counter - 1;
            end
            if(rcd_counter != 0 && (cmd_read | cmd_write | cmd_read_pre | cmd_write_pre))begin
                $display("[%t] tRCD violation", $time);
            end

            if(cmd_write | cmd_write_pre)begin
                dpl_counter <= tDPL - 1;
            end
            else if(dpl_counter != 0) begin
                dpl_counter <= dpl_counter - 1;
            end
            if(dpl_counter != 0 && cmd_pre)begin
                $display("[%t] tDPL violation", $time);
            end

            if(cmd_write | cmd_write_pre)begin
                dal_counter <= tDAL - 1;
            end
            else if(dal_counter != 0) begin
                dal_counter <= dal_counter - 1;
            end
            if(dal_counter != 0 && (cmd_act | cmd_self))begin
                $display("[%t] tDAL violation", $time);
            end

            if(cmd_mrs)begin
                mrd_counter <= tMRD - 1;
            end
            else if(mrd_counter != 0) begin
                mrd_counter <= mrd_counter - 1;
            end
            if(mrd_counter != 0 && !cmd_nop)begin
                $display("[%t] tMRD violation", $time);
            end

            if(cmd_act)begin
                ras_counter <= tRAS - 1;
            end
            else if(ras_counter != 0) begin
                ras_counter <= ras_counter - 1;
            end
            if(ras_counter != 0 && cmd_pre)begin
                $display("[%t] tRAS violation", $time);
            end
        end
    end


endmodule